#include <sstream>
#include "storage.h"
#include "dal/dalexcept.h"
#include "dal/dataproviderfactory.h"
#include "utils/throwerror.h"
#include "utils/xml.h"

static const char *DEFAULT_ITEM_FILE = "items.xml";
static const char *DB_VERSION_PARAMETER = "database_version";
static const char *SUPPORTED_DB_VERSION = "14";

static const char *ACCOUNTS_TBL_NAME            =   "mana_accounts";
static const char *CHARACTERS_TBL_NAME          =   "mana_characters";
static const char *CHAR_ATTR_TBL_NAME           =   "mana_char_attr";
static const char *CHAR_SKILLS_TBL_NAME         =   "mana_char_skills";
static const char *CHAR_STATUS_EFFECTS_TBL_NAME =   "mana_char_status_effects";
static const char *CHAR_KILL_COUNT_TBL_NAME     =   "mana_char_kill_stats";
static const char *CHAR_SPECIALS_TBL_NAME       =   "mana_char_specials";
static const char *CHAR_EQUIPS_TBL_NAME         =   "mana_char_equips";
static const char *INVENTORIES_TBL_NAME         =   "mana_inventories";
static const char *ITEMS_TBL_NAME               =   "mana_items";
static const char *GUILDS_TBL_NAME              =   "mana_guilds";
static const char *GUILD_MEMBERS_TBL_NAME       =   "mana_guild_members";
static const char *QUESTS_TBL_NAME              =   "mana_quests";
static const char *WORLD_STATES_TBL_NAME        =   "mana_world_states";
static const char *POST_TBL_NAME                =   "mana_post";
static const char *POST_ATTACHMENTS_TBL_NAME    =   "mana_post_attachments";
static const char *AUCTION_TBL_NAME             =   "mana_auctions";
static const char *AUCTION_BIDS_TBL_NAME        =   "mana_auction_bids";
static const char *ONLINE_USERS_TBL_NAME        =   "mana_online_list";
static const char *TRANSACTION_TBL_NAME         =   "mana_transactions";

Storage::Storage()
        : mDb(dal::DataProviderFactory::createDataProvider()),
          mItemDbVersion(0)
{
}

Storage::~Storage()
{
    if (mDb->isConnected())
        close();

    delete mDb;
}

void Storage::open()
{
    // Do nothing if already connected.
    if (mDb->isConnected())
        return;

    using namespace dal;

    try
    {
        // Open a connection to the database.
        mDb->connect();

        // Check database version here
        std::string dbversion = getWorldStateVar(DB_VERSION_PARAMETER);
        if (dbversion != SUPPORTED_DB_VERSION)
        {
            std::ostringstream errmsg;
            errmsg << "Database version is not supported. "
                   << "Needed version: '" << SUPPORTED_DB_VERSION
                   << "', current version: '" << dbversion << "'";
            utils::throwError(errmsg.str());
        }

        LOG_INFO("DBVer:" << dbversion)

        // Synchronize base data from xml files
        syncDatabase();

        // Clean list of online users, this should be empty after restart
        std::ostringstream sql;
        sql << "DELETE FROM " << ONLINE_USERS_TBL_NAME;
        mDb->execSql(sql.str());
    }
    catch (const DbConnectionFailure& e)
    {
        utils::throwError("(DALStorage::open) "
                          "Unable to connect to the database: ", e);
    }
}

void Storage::close()
{
    mDb->disconnect();
}

std::string Storage::getWorldStateVar(const std::string &name, int mapId)
{
    try
    {
        std::ostringstream query;
        query << "SELECT `value` "
              << "FROM " << WORLD_STATES_TBL_NAME
              << " WHERE `state_name` = ?";

        // Add map filter if map_id is given
        if (mapId >= 0)
            query << " AND `map_id` = ?";

        //query << ";"; <-- No ';' at the end of prepared statements.
        //LOG_INFO(query.str() << name);

        if (mDb->prepareSql(query.str()))
        {
            mDb->bindValue(1, name);
            if (mapId >= 0)
                mDb->bindValue(2, mapId);
            const dal::RecordSet &info = mDb->processSql();

            if (!info.isEmpty())
                return info(0, 0);
        }
        else
        {
            utils::throwError("(DALStorage:getWorldStateVar) "
                              "SQL query preparation failure.");
        }
    }
    catch (const dal::DbSqlQueryExecFailure &e)
    {
        utils::throwError("(DALStorage::getWorldStateVar) SQL query failure: ",
                          e);
    }

    return std::string();
}

void Storage::syncDatabase()
{
    XML::Document doc(DEFAULT_ITEM_FILE);
    xmlNodePtr rootNode = doc.rootNode();

    if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items"))
    {
        std::ostringstream errMsg;
        errMsg << "Item Manager: Error while loading item database"
               << "(" << DEFAULT_ITEM_FILE << ")!";
        LOG_ERROR(errMsg.str());
        return;
    }

    dal::PerformTransaction transaction(mDb);

    std::ostringstream sql;
    sql << "DELETE FROM " << ITEMS_TBL_NAME;
    //mDb->execSql(sql.str());

    if (mDb->prepareSql(sql.str()))
    {
        mDb->processSql();
    }

    int itemCount = 0;
    for_each_xml_child_node(node, rootNode)
    {
        // Try to load the version of the item database.
        if (xmlStrEqual(node->name, BAD_CAST "version"))
        {
            std::string revision = XML::getProperty(node, "revision",
                                                    std::string());
            mItemDbVersion = atoi(revision.c_str());
            LOG_INFO("Loading item database version " << mItemDbVersion);
        }

        if (!xmlStrEqual(node->name, BAD_CAST "item"))
            continue;

        if (xmlStrEqual(node->name, BAD_CAST "item"))
        {
            int id = XML::getProperty(node, "id", 0);

            if (id < 1)
                continue;

            int weight = XML::getProperty(node, "weight", 0);
            std::string type = XML::getProperty(node, "type", std::string());
            std::string name = XML::getProperty(node, "name", std::string());
            std::string desc = XML::getProperty(node, "description",
                                                std::string());
            std::string eff  = XML::getProperty(node, "effect", std::string());
            std::string image = XML::getProperty(node, "image", std::string());
            std::string dye;

            // Split image name and dye string
            size_t pipe = image.find("|");
            if (pipe != std::string::npos)
            {
                dye = image.substr(pipe + 1);
                image = image.substr(0, pipe);
            }

            try
            {
                sql.clear();
                sql.str("");
                sql << "INSERT INTO " << ITEMS_TBL_NAME
                    << " VALUES ( " << id
                    << ",  '" << name
                    << "', '" << desc
                    << "', '" << image
                    << "',  " << weight
                    << ",  '" << type
                    << "', '" << eff
                    << "', '" << dye
                    << "' )";

                if (mDb->prepareSql(sql.str()))
                {
                    mDb->processSql();
                }
                else
                {
                    utils::throwError("(DALStorage::SyncDatabase) "
                                   "SQL query preparation failure #2.");
                }

                itemCount++;
            }
            catch (const dal::DbSqlQueryExecFailure &e)
            {
                utils::throwError("(DALStorage::SyncDatabase) "
                                  "SQL query failure: ", e);
            }
        }
    }

    transaction.commit();
}

